#pragma once

#include <optional>
#include <string>

#include "byteStream.hpp"
#include "nlohmann/json.hpp"
#include "serverApdu.hpp"
#include "strategy.hpp"
#include <variant>

inline void from_json(const nlohmann::json &j, std::variant<int, std::string> &s) {
    if (j.is_number()) {
        s = j.get<int>();
    } else if (j.is_string()) {
        s = j.get<std::string>();
    }
}

class Point {
private:
    static inline constexpr std::size_t defaultWaitTimeout = 1000;
    static inline constexpr double defaultEfficient = 1.0;

    static inline std::string NULLType = "NULL";
    static inline std::string arrayType = "array";
    static inline std::string structureType = "structure";
    static inline std::string boolType = "bool";
    static inline std::string bitStringType = "bit-string";
    static inline std::string doubleLongType = "double-long";
    static inline std::string doubleLongUnsignedType = "double-long-unsigned";
    static inline std::string octetStringType = "octet-string";
    static inline std::string visibleStringType = "visible-string";
    static inline std::string utf8StringType = "UTF8-string";
    static inline std::string integerType = "integer";
    static inline std::string longType = "long";
    static inline std::string unsignedType = "unsigned";
    static inline std::string longUnsignedType = "long-unsigned";
    static inline std::string long64Type = "long64";
    static inline std::string long64UnsignedType = "long64-unsigned";
    static inline std::string enumType = "enum";
    static inline std::string float32Type = "float32";
    static inline std::string float64Type = "float64";
    static inline std::string dateTimeType = "date_time";
    static inline std::string dateType = "date";
    static inline std::string timeType = "time";
    static inline std::string dateTimeSType = "date_time_s";
    static inline std::string OIType = "OI";
    static inline std::string OADType = "OAD";
    static inline std::string ROADType = "ROAD";
    static inline std::string OMDType = "OMD";
    static inline std::string TIType = "TI";
    static inline std::string TSAType = "TSA";
    static inline std::string MACType = "MAC";
    static inline std::string RNType = "RN";
    static inline std::string RegionType = "Region";
    static inline std::string ScalerUnitType = "Scaler_Unit";
    static inline std::string RSDType = "RSD";
    static inline std::string CSDType = "CSD";
    static inline std::string MSType = "MS";
    static inline std::string SIDType = "SID";
    static inline std::string SIDMACType = "SID_MAC";
    static inline std::string COMDCBType = "COMDCB";
    static inline std::string RCSDType = "RCS";

private:
    using Action = std::function<void(void *, Point *, const ByteStream &, const ByteStream &)>;

private:
    std::string _name;
    std::string _dataType;
    std::string _unit;
    std::optional<double> _efficient;
    std::string _oad;
    std::optional<std::size_t> _operatorCode;
    std::optional<std::size_t> _wait;

    ServerApdu::DataValueType _data;
    std::vector<Strategy> _sync;
    std::vector<Action> _readCallback;
    bool _operatorResult{false};

    std::vector<std::pair<std::variant<int, std::string>, std::variant<int, std::string>>> _map;

public:
    bool hasName(void) const { return !_name.empty(); }
    bool hasDataType(void) const { return !_dataType.empty(); }
    bool hasUnit(void) const { return !_unit.empty(); }
    bool hasEfficient(void) const { return _efficient.has_value(); }
    bool hasOad(void) const { return !_oad.empty(); }
    bool hasOperatorCode(void) const { return _operatorCode.has_value(); }
    bool hasWaitTime(void) const { return _wait.has_value(); }
    bool hasData(void) const { return !_data.empty(); }
    bool hasStrategy(void) const { return !_sync.empty(); }
    bool hasChangeStrategy(void) const {
        return std::any_of(_sync.begin(), _sync.end(), [](const Strategy &s) { return s.isChangeStrategy(); });
    }

    bool hasLoopStrategy(void) const {
        return std::any_of(_sync.begin(), _sync.end(), [](const Strategy &s) { return s.isLoopStrategy(); });
    }

    bool hasLoopTime(void) const {
        for (const auto &s : _sync) {
            if (s.isLoopStrategy()) {
                return s.hasParam();
            }
        }
        return false;
    }

    bool hasMap(void) const { return !_map.empty(); }

    std::optional<std::variant<int, std::string>> mapValue(const std::variant<int, std::string> &key) const {
        auto it = std::find_if(_map.begin(), _map.end(), [key](const auto &item) { return item.first == key; });
        if (it == _map.end()) {
            return std::nullopt;
        }

        return it->second;
    }

    std::size_t loopTime(void) const {
        for (const auto &s : _sync) {
            if (s.isLoopStrategy()) {
                return s.param();
            }
        }
        return 0;
    }

    std::string loopUuid(void) const {
        for (const auto &s : _sync) {
            if (s.isLoopStrategy()) {
                return s.uuid();
            }
        }
        return {};
    }

    bool readAble(void) const { return hasOperatorCode() && (_operatorCode == 4 || _operatorCode == 1 || _operatorCode == 2 || _operatorCode == 3); }
    bool writeAble(void) const { return hasOperatorCode() && (_operatorCode == 5 || _operatorCode == 6 || _operatorCode == 7 || _operatorCode == 8); }
    bool actionAble(void) const { return hasOperatorCode() && (_operatorCode == 9 || _operatorCode == 10 || _operatorCode == 11 || _operatorCode == 12); }
    bool hasReadCallback(void) const { return !_readCallback.empty(); }

public:
    bool isArrayType(void) const { return _dataType == arrayType; }
    bool isNullType(void) const { return _dataType == NULLType; }
    bool isStructureType(void) const { return _dataType == structureType; }
    bool isBoolType(void) const { return _dataType == boolType; }
    bool isBitStringType(void) const { return _dataType == bitStringType; }
    bool isDoubleLongType(void) const { return _dataType == doubleLongType; }
    bool isDoubleLongUnsignedType(void) const { return _dataType == doubleLongUnsignedType; }
    bool isOctetStringType(void) const { return _dataType == octetStringType; }
    bool isVisibleStringType(void) const { return _dataType == visibleStringType; }
    bool isUtf8StringType(void) const { return _dataType == utf8StringType; }
    bool isIntegerType(void) const { return _dataType == integerType; }
    bool isLongType(void) const { return _dataType == longType; }
    bool isUnsignedType(void) const { return _dataType == unsignedType; }
    bool isLongUnsignedType(void) const { return _dataType == longUnsignedType; }
    bool isLong64Type(void) const { return _dataType == long64Type; }
    bool isLong64UnsignedType(void) const { return _dataType == long64UnsignedType; }
    bool isEnumType(void) const { return _dataType == enumType; }
    bool isFloat32Type(void) const { return _dataType == float32Type; }
    bool isFloat64Type(void) const { return _dataType == float64Type; }
    bool isDateTimeType(void) const { return _dataType == dateTimeType; }
    bool isDateType(void) const { return _dataType == dateType; }
    bool isTimeType(void) const { return _dataType == timeType; }
    bool isDateTimeSType(void) const { return _dataType == dateTimeSType; }
    bool isOIType(void) const { return _dataType == OIType; }
    bool isOADType(void) const { return _dataType == OADType; }
    bool isROADType(void) const { return _dataType == ROADType; }
    bool isOMDType(void) const { return _dataType == OMDType; }
    bool isTIType(void) const { return _dataType == TIType; }
    bool isTSAType(void) const { return _dataType == TSAType; }
    bool isMACType(void) const { return _dataType == MACType; }
    bool isRNType(void) const { return _dataType == RNType; }
    bool isRegionType(void) const { return _dataType == RegionType; }
    bool isScalerUnitType(void) const { return _dataType == ScalerUnitType; }
    bool isRSDType(void) const { return _dataType == RSDType; }
    bool isCSDType(void) const { return _dataType == CSDType; }
    bool isMSType(void) const { return _dataType == MSType; }
    bool isSIDType(void) const { return _dataType == SIDType; }
    bool isSIDMACType(void) const { return _dataType == SIDMACType; }
    bool isCOMDCBType(void) const { return _dataType == COMDCBType; }
    bool isRCSDType(void) const { return _dataType == RCSDType; }

    bool isNormalRequest(void) const { return _operatorCode == 1; }
    bool isSecurityRequest(void) const { return _operatorCode == 2; }

public:
    const std::string &name(void) const { return _name; }
    const std::string &dataType(void) const { return _dataType; }
    const std::string &unit(void) const { return _unit; }

    bool operatorSuccess(void) { return (_operatorResult = true); }
    bool operatorFail(void) { return (_operatorResult = false); }
    bool operatorResult(void) const { return _operatorResult; }

    template <typename F, typename... Args>
    Point &registerReadCallback(F &&f, Args &&... args) {
        _readCallback.emplace_back(std::bind(std::forward<F>(f), std::forward<Args>(args)..., std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
        return *this;
    }

    template <class T1, class T2, class T3, class T4>
    void readCallback(T1 &&arg1, T2 &&arg2, T3 &&arg3, T4 &&arg4) {
        std::for_each(_readCallback.begin(), _readCallback.end(), [&](const auto &f) { f(std::forward<T1>(arg1), std::forward<T2>(arg2), std::forward<T3>(arg3), std::forward<T4>(arg4)); });
    }

    void setData(const ServerApdu::DataValueType &v) { _data = v; }

    ByteStream rawData(void) const {
        if (hasData()) {
            return _data[0].second;
        }

        return {};
    }

    double efficient(void) const {
        if (hasEfficient()) {
            return _efficient.value();
        }
        return defaultEfficient;
    }

    ByteStream oad(void) const {
        uint32_t o[4]{};
        std::sscanf(_oad.c_str(), "%02x-%02x-%02x-%02x", &o[0], &o[1], &o[2], &o[3]);
        return ByteStream{static_cast<uint8_t>(o[0]), static_cast<uint8_t>(o[1]), static_cast<uint8_t>(o[2]), static_cast<uint8_t>(o[3])};
    }
    std::size_t operatorCode(void) const { return _operatorCode.value(); }
    std::size_t waitTimeout(void) const {
        if (hasWaitTime()) {
            return _wait.value();
        }
        return defaultWaitTimeout;
    }

    friend void from_json(const nlohmann::json &j, Point &p) {
        if (j.contains("key")) {
            j.at("key").get_to(p._name);
        }

        if (j.contains("north") && j.at("north").contains("sync") && j.at("north").at("sync").is_array()) {
            for (const auto &strategy : j.at("north").at("sync")) {
                p._sync.emplace_back(strategy.get<Strategy>());
            }
        }

        if (j.contains("south") && j.at("south").contains("point")) {
            auto point = j.at("south").at("point");
            if (point.contains("wait")) {
                p._wait = point.at("wait").get<std::size_t>();
            }

            if (point.contains("dataType")) {
                point.at("dataType").get_to(p._dataType);
            }

            if (point.contains("efficient")) {
                p._efficient = point.at("efficient").get<double>();
            }

            if (point.contains("OAD")) {
                point.at("OAD").get_to(p._oad);
            }

            if (point.contains("operationCode")) {
                p._operatorCode = point.at("operationCode").get<std::size_t>();
            }

            if (point.contains("map") && point.at("map").is_array()) {
                for (const auto &item : point.at("map")) {
                    std::variant<int, std::string> key, val;
                    if (item.contains("key")) {
                        if (item.at("key").is_number_integer()) {
                            key = item.at("key").get<int>();
                        } else if (item.at("key").is_string()) {
                            key = item.at("key").get<std::string>();
                        }
                    }

                    if (item.contains("val")) {
                        if (item.at("val").is_number_integer()) {
                            val = item.at("val").get<int>();
                        } else if (item.at("val").is_string()) {
                            val = item.at("val").get<std::string>();
                        }
                    }

                    p._map.emplace_back(key, val);
                }
            }
        }
    }

    std::any pointValue(const ByteStream &b) const {
        if (isDoubleLongType() || isLongType()) {
            return hasEfficient() ? b.get<int64_t>() * efficient() : b.get<int64_t>();
        } else if (isDoubleLongUnsignedType() || isLongUnsignedType() || isLong64UnsignedType()) {
            return hasEfficient() ? b.get<uint64_t>() * efficient() : b.get<uint64_t>();
        }

        return b.toHexString();
    }

    std::any value(void) const {
        if (_data.size() != 1) {
            return pointValue({});
        }

        return pointValue(_data.at(0).second);
    }
};
